import { createHmac } from 'crypto'
import { db } from '@sim/db'
import {
  account,
  workflow as workflowTable,
  workspaceNotificationDelivery,
  workspaceNotificationSubscription,
} from '@sim/db/schema'
import { task } from '@trigger.dev/sdk'
import { and, eq, isNull, lte, or, sql } from 'drizzle-orm'
import { v4 as uuidv4 } from 'uuid'
import { checkUsageStatus } from '@/lib/billing/calculations/usage-monitor'
import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
import { decryptSecret } from '@/lib/core/security/encryption'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { createLogger } from '@/lib/logs/console/logger'
import type { TraceSpan, WorkflowExecutionLog } from '@/lib/logs/types'
import { sendEmail } from '@/lib/messaging/email/mailer'
import type { AlertConfig } from '@/lib/notifications/alert-rules'
import { RateLimiter } from '@/services/queue'

const logger = createLogger('WorkspaceNotificationDelivery')

const MAX_ATTEMPTS = 5
const RETRY_DELAYS = [5 * 1000, 15 * 1000, 60 * 1000, 3 * 60 * 1000, 10 * 60 * 1000]

function getRetryDelayWithJitter(baseDelay: number): number {
  const jitter = Math.random() * 0.1 * baseDelay
  return Math.floor(baseDelay + jitter)
}

interface NotificationPayload {
  id: string
  type: 'workflow.execution.completed'
  timestamp: number
  data: {
    workflowId: string
    workflowName?: string
    executionId: string
    status: 'success' | 'error'
    level: string
    trigger: string
    startedAt: string
    endedAt: string
    totalDurationMs: number
    cost?: Record<string, unknown>
    finalOutput?: unknown
    traceSpans?: unknown[]
    rateLimits?: Record<string, unknown>
    usage?: Record<string, unknown>
  }
}

function generateSignature(secret: string, timestamp: number, body: string): string {
  const signatureBase = `${timestamp}.${body}`
  const hmac = createHmac('sha256', secret)
  hmac.update(signatureBase)
  return hmac.digest('hex')
}

async function buildPayload(
  log: WorkflowExecutionLog,
  subscription: typeof workspaceNotificationSubscription.$inferSelect
): Promise<NotificationPayload> {
  const workflowData = await db
    .select({ name: workflowTable.name, userId: workflowTable.userId })
    .from(workflowTable)
    .where(eq(workflowTable.id, log.workflowId))
    .limit(1)

  const timestamp = Date.now()
  const executionData = (log.executionData || {}) as Record<string, unknown>
  const userId = workflowData[0]?.userId

  const payload: NotificationPayload = {
    id: `evt_${uuidv4()}`,
    type: 'workflow.execution.completed',
    timestamp,
    data: {
      workflowId: log.workflowId,
      workflowName: workflowData[0]?.name || 'Unknown Workflow',
      executionId: log.executionId,
      status: log.level === 'error' ? 'error' : 'success',
      level: log.level,
      trigger: log.trigger,
      startedAt: log.startedAt,
      endedAt: log.endedAt,
      totalDurationMs: log.totalDurationMs,
      cost: log.cost as Record<string, unknown>,
    },
  }

  if (subscription.includeFinalOutput && executionData.finalOutput) {
    payload.data.finalOutput = executionData.finalOutput
  }

  if (subscription.includeTraceSpans && executionData.traceSpans) {
    payload.data.traceSpans = executionData.traceSpans as unknown[]
  }

  if (subscription.includeRateLimits && userId) {
    try {
      const userSubscription = await getHighestPrioritySubscription(userId)
      const rateLimiter = new RateLimiter()
      const triggerType = log.trigger === 'api' ? 'api' : 'manual'

      const [syncStatus, asyncStatus] = await Promise.all([
        rateLimiter.getRateLimitStatusWithSubscription(
          userId,
          userSubscription,
          triggerType,
          false
        ),
        rateLimiter.getRateLimitStatusWithSubscription(userId, userSubscription, triggerType, true),
      ])

      payload.data.rateLimits = {
        sync: {
          limit: syncStatus.limit,
          remaining: syncStatus.remaining,
          resetAt: syncStatus.resetAt.toISOString(),
        },
        async: {
          limit: asyncStatus.limit,
          remaining: asyncStatus.remaining,
          resetAt: asyncStatus.resetAt.toISOString(),
        },
      }
    } catch (error) {
      logger.warn('Failed to fetch rate limits for notification', { error, userId })
    }
  }

  if (subscription.includeUsageData && userId) {
    try {
      const usageData = await checkUsageStatus(userId)
      payload.data.usage = {
        currentPeriodCost: usageData.currentUsage,
        limit: usageData.limit,
        percentUsed: usageData.percentUsed,
        isExceeded: usageData.isExceeded,
      }
    } catch (error) {
      logger.warn('Failed to fetch usage data for notification', { error, userId })
    }
  }

  return payload
}

interface WebhookConfig {
  url: string
  secret?: string
}

interface SlackConfig {
  channelId: string
  channelName: string
  accountId: string
}

async function deliverWebhook(
  subscription: typeof workspaceNotificationSubscription.$inferSelect,
  payload: NotificationPayload
): Promise<{ success: boolean; status?: number; error?: string }> {
  const webhookConfig = subscription.webhookConfig as WebhookConfig | null
  if (!webhookConfig?.url) {
    return { success: false, error: 'No webhook URL configured' }
  }

  const body = JSON.stringify(payload)
  const deliveryId = `delivery_${uuidv4()}`
  const headers: Record<string, string> = {
    'Content-Type': 'application/json',
    'sim-event': 'workflow.execution.completed',
    'sim-timestamp': payload.timestamp.toString(),
    'sim-delivery-id': deliveryId,
    'Idempotency-Key': deliveryId,
  }

  if (webhookConfig.secret) {
    const { decrypted } = await decryptSecret(webhookConfig.secret)
    const signature = generateSignature(decrypted, payload.timestamp, body)
    headers['sim-signature'] = `t=${payload.timestamp},v1=${signature}`
  }

  const controller = new AbortController()
  const timeoutId = setTimeout(() => controller.abort(), 30000)

  try {
    const response = await fetch(webhookConfig.url, {
      method: 'POST',
      headers,
      body,
      signal: controller.signal,
    })

    clearTimeout(timeoutId)

    return {
      success: response.ok,
      status: response.status,
      error: response.ok ? undefined : `HTTP ${response.status}`,
    }
  } catch (error: unknown) {
    clearTimeout(timeoutId)
    const err = error as Error & { name?: string }
    return {
      success: false,
      error: err.name === 'AbortError' ? 'Request timeout' : err.message,
    }
  }
}

function formatDuration(ms: number): string {
  if (ms < 1000) return `${ms}ms`
  if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`
  return `${(ms / 60000).toFixed(1)}m`
}

function formatCost(cost?: Record<string, unknown>): string {
  if (!cost?.total) return 'N/A'
  const total = cost.total as number
  return `$${total.toFixed(4)}`
}

function buildLogUrl(workspaceId: string, executionId: string): string {
  return `${getBaseUrl()}/workspace/${workspaceId}/logs?search=${encodeURIComponent(executionId)}`
}

function formatAlertReason(alertConfig: AlertConfig): string {
  switch (alertConfig.rule) {
    case 'consecutive_failures':
      return `${alertConfig.consecutiveFailures} consecutive failures detected`
    case 'failure_rate':
      return `Failure rate exceeded ${alertConfig.failureRatePercent}% over ${alertConfig.windowHours}h`
    case 'latency_threshold':
      return `Execution exceeded ${Math.round((alertConfig.durationThresholdMs || 0) / 1000)}s duration threshold`
    case 'latency_spike':
      return `Execution was ${alertConfig.latencySpikePercent}% slower than average`
    case 'cost_threshold':
      return `Execution cost exceeded $${alertConfig.costThresholdDollars} threshold`
    case 'no_activity':
      return `No workflow activity detected in ${alertConfig.inactivityHours}h`
    case 'error_count':
      return `${alertConfig.errorCountThreshold} errors detected in ${alertConfig.windowHours}h window`
    default:
      return 'Alert condition met'
  }
}

function formatJsonForEmail(data: unknown, label: string): string {
  if (!data) return ''
  const json = JSON.stringify(data, null, 2)
  const escapedJson = json.replace(/</g, '&lt;').replace(/>/g, '&gt;')
  return `
    <div style="margin-top: 20px;">
      <h3 style="color: #1a1a1a; font-size: 14px; margin-bottom: 8px;">${label}</h3>
      <pre style="background: #f5f5f5; padding: 12px; border-radius: 6px; overflow-x: auto; font-size: 12px; color: #333; white-space: pre-wrap; word-wrap: break-word;">${escapedJson}</pre>
    </div>
  `
}

async function deliverEmail(
  subscription: typeof workspaceNotificationSubscription.$inferSelect,
  payload: NotificationPayload,
  alertConfig?: AlertConfig
): Promise<{ success: boolean; error?: string }> {
  if (!subscription.emailRecipients || subscription.emailRecipients.length === 0) {
    return { success: false, error: 'No email recipients configured' }
  }

  const isError = payload.data.status !== 'success'
  const statusText = isError ? 'Error' : 'Success'
  const logUrl = buildLogUrl(subscription.workspaceId, payload.data.executionId)
  const baseUrl = getBaseUrl()
  const alertReason = alertConfig ? formatAlertReason(alertConfig) : null

  // Build subject line
  const subject = alertReason
    ? `Alert: ${payload.data.workflowName}`
    : isError
      ? `Error Alert: ${payload.data.workflowName}`
      : `Workflow Completed: ${payload.data.workflowName}`

  let includedDataHtml = ''
  let includedDataText = ''

  if (payload.data.finalOutput) {
    includedDataHtml += formatJsonForEmail(payload.data.finalOutput, 'Final Output')
    includedDataText += `\n\nFinal Output:\n${JSON.stringify(payload.data.finalOutput, null, 2)}`
  }

  if (
    payload.data.traceSpans &&
    Array.isArray(payload.data.traceSpans) &&
    payload.data.traceSpans.length > 0
  ) {
    includedDataHtml += formatJsonForEmail(payload.data.traceSpans, 'Trace Spans')
    includedDataText += `\n\nTrace Spans:\n${JSON.stringify(payload.data.traceSpans, null, 2)}`
  }

  if (payload.data.rateLimits) {
    includedDataHtml += formatJsonForEmail(payload.data.rateLimits, 'Rate Limits')
    includedDataText += `\n\nRate Limits:\n${JSON.stringify(payload.data.rateLimits, null, 2)}`
  }

  if (payload.data.usage) {
    includedDataHtml += formatJsonForEmail(payload.data.usage, 'Usage Data')
    includedDataText += `\n\nUsage Data:\n${JSON.stringify(payload.data.usage, null, 2)}`
  }

  const result = await sendEmail({
    to: subscription.emailRecipients,
    subject,
    html: `
      <!DOCTYPE html>
      <html>
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
        </head>
        <body style="background-color: #f5f5f7; font-family: HelveticaNeue, Helvetica, Arial, sans-serif; margin: 0; padding: 0;">
          <div style="max-width: 580px; margin: 30px auto; background-color: #ffffff; border-radius: 5px; overflow: hidden;">
            <!-- Header with Logo -->
            <div style="padding: 30px 0; text-align: center;">
              <img src="${baseUrl}/logo/reverse/text/medium.png" width="114" alt="Sim Studio" style="margin: 0 auto;" />
            </div>
            
            <!-- Section Border -->
            <div style="display: flex; width: 100%;">
              <div style="border-bottom: 1px solid #eeeeee; width: 249px;"></div>
              <div style="border-bottom: 1px solid #6F3DFA; width: 102px;"></div>
              <div style="border-bottom: 1px solid #eeeeee; width: 249px;"></div>
            </div>
            
            <!-- Content -->
            <div style="padding: 5px 30px 20px 30px;">
              <h2 style="font-size: 20px; color: #333333; margin: 20px 0;">
                ${alertReason ? 'Alert Triggered' : isError ? 'Workflow Execution Failed' : 'Workflow Execution Completed'}
              </h2>
              ${alertReason ? `<p style="color: #d97706; background: #fef3c7; padding: 12px; border-radius: 6px; margin-bottom: 20px; font-size: 14px;"><strong>Reason:</strong> ${alertReason}</p>` : ''}
              
              <table style="width: 100%; border-collapse: collapse; margin-bottom: 20px;">
                <tr style="border-bottom: 1px solid #eee;">
                  <td style="padding: 12px 0; color: #666; width: 140px;">Workflow</td>
                  <td style="padding: 12px 0; color: #333; font-weight: 500;">${payload.data.workflowName}</td>
                </tr>
                <tr style="border-bottom: 1px solid #eee;">
                  <td style="padding: 12px 0; color: #666;">Status</td>
                  <td style="padding: 12px 0; color: ${isError ? '#ef4444' : '#22c55e'}; font-weight: 500;">${statusText}</td>
                </tr>
                <tr style="border-bottom: 1px solid #eee;">
                  <td style="padding: 12px 0; color: #666;">Trigger</td>
                  <td style="padding: 12px 0; color: #333;">${payload.data.trigger}</td>
                </tr>
                <tr style="border-bottom: 1px solid #eee;">
                  <td style="padding: 12px 0; color: #666;">Duration</td>
                  <td style="padding: 12px 0; color: #333;">${formatDuration(payload.data.totalDurationMs)}</td>
                </tr>
                <tr style="border-bottom: 1px solid #eee;">
                  <td style="padding: 12px 0; color: #666;">Cost</td>
                  <td style="padding: 12px 0; color: #333;">${formatCost(payload.data.cost)}</td>
                </tr>
              </table>
              
              <a href="${logUrl}" style="display: inline-block; background-color: #6F3DFA; color: #ffffff; font-weight: bold; font-size: 16px; padding: 12px 30px; border-radius: 5px; text-decoration: none; text-align: center; margin: 20px 0;">
                View Execution Log →
              </a>
              
              ${includedDataHtml}
              
              <p style="font-size: 16px; line-height: 1.5; color: #333333; margin-top: 30px;">
                Best regards,<br />
                The Sim Team
              </p>
            </div>
          </div>
          
          <!-- Footer -->
          <div style="max-width: 580px; margin: 0 auto; padding: 20px 0; text-align: center;">
            <p style="font-size: 12px; color: #706a7b; margin: 8px 0 0 0;">
              © ${new Date().getFullYear()} Sim Studio, All Rights Reserved
            </p>
            <p style="font-size: 12px; color: #706a7b; margin: 8px 0 0 0;">
              <a href="${baseUrl}/privacy" style="color: #706a7b; text-decoration: underline;">Privacy Policy</a> • 
              <a href="${baseUrl}/terms" style="color: #706a7b; text-decoration: underline;">Terms of Service</a>
            </p>
          </div>
        </body>
      </html>
    `,
    text: `${subject}\n${alertReason ? `\nReason: ${alertReason}\n` : ''}\nWorkflow: ${payload.data.workflowName}\nStatus: ${statusText}\nTrigger: ${payload.data.trigger}\nDuration: ${formatDuration(payload.data.totalDurationMs)}\nCost: ${formatCost(payload.data.cost)}\n\nView Log: ${logUrl}${includedDataText}`,
    emailType: 'notifications',
  })

  return { success: result.success, error: result.success ? undefined : result.message }
}

async function deliverSlack(
  subscription: typeof workspaceNotificationSubscription.$inferSelect,
  payload: NotificationPayload,
  alertConfig?: AlertConfig
): Promise<{ success: boolean; error?: string }> {
  const slackConfig = subscription.slackConfig as SlackConfig | null
  if (!slackConfig?.channelId || !slackConfig?.accountId) {
    return { success: false, error: 'No Slack channel or account configured' }
  }

  const [slackAccount] = await db
    .select({ accessToken: account.accessToken, userId: account.userId })
    .from(account)
    .where(eq(account.id, slackConfig.accountId))
    .limit(1)

  if (!slackAccount?.accessToken) {
    return { success: false, error: 'Slack account not found or not connected' }
  }

  const alertReason = alertConfig ? formatAlertReason(alertConfig) : null
  const statusEmoji = alertReason
    ? ':warning:'
    : payload.data.status === 'success'
      ? ':white_check_mark:'
      : ':x:'
  const statusColor = alertReason
    ? '#d97706'
    : payload.data.status === 'success'
      ? '#22c55e'
      : '#ef4444'
  const logUrl = buildLogUrl(subscription.workspaceId, payload.data.executionId)

  const blocks: Array<Record<string, unknown>> = []

  if (alertReason) {
    blocks.push({
      type: 'section',
      text: {
        type: 'mrkdwn',
        text: `*Reason:* ${alertReason}`,
      },
    })
  }

  blocks.push(
    {
      type: 'section',
      fields: [
        { type: 'mrkdwn', text: `*Status:*\n${payload.data.status}` },
        { type: 'mrkdwn', text: `*Trigger:*\n${payload.data.trigger}` },
        { type: 'mrkdwn', text: `*Duration:*\n${formatDuration(payload.data.totalDurationMs)}` },
        { type: 'mrkdwn', text: `*Cost:*\n${formatCost(payload.data.cost)}` },
      ],
    },
    {
      type: 'actions',
      elements: [
        {
          type: 'button',
          text: { type: 'plain_text', text: 'View Log →', emoji: true },
          url: logUrl,
          style: 'primary',
        },
      ],
    }
  )

  if (payload.data.finalOutput) {
    const outputStr = JSON.stringify(payload.data.finalOutput, null, 2)
    const truncated = outputStr.length > 2900 ? `${outputStr.slice(0, 2900)}...` : outputStr
    blocks.push({
      type: 'section',
      text: {
        type: 'mrkdwn',
        text: `*Final Output:*\n\`\`\`${truncated}\`\`\``,
      },
    })
  }

  if (
    payload.data.traceSpans &&
    Array.isArray(payload.data.traceSpans) &&
    payload.data.traceSpans.length > 0
  ) {
    const spansSummary = (payload.data.traceSpans as TraceSpan[])
      .map((span) => {
        const status = span.status === 'success' ? '✓' : '✗'
        return `${status} ${span.name || 'Unknown'} (${formatDuration(span.duration || 0)})`
      })
      .join('\n')
    blocks.push({
      type: 'section',
      text: {
        type: 'mrkdwn',
        text: `*Trace Spans:*\n\`\`\`${spansSummary}\`\`\``,
      },
    })
  }

  if (payload.data.rateLimits) {
    const limitsStr = JSON.stringify(payload.data.rateLimits, null, 2)
    blocks.push({
      type: 'section',
      text: {
        type: 'mrkdwn',
        text: `*Rate Limits:*\n\`\`\`${limitsStr}\`\`\``,
      },
    })
  }

  if (payload.data.usage) {
    const usageStr = JSON.stringify(payload.data.usage, null, 2)
    blocks.push({
      type: 'section',
      text: {
        type: 'mrkdwn',
        text: `*Usage Data:*\n\`\`\`${usageStr}\`\`\``,
      },
    })
  }

  blocks.push({
    type: 'context',
    elements: [{ type: 'mrkdwn', text: `Execution ID: \`${payload.data.executionId}\`` }],
  })

  const fallbackText = alertReason
    ? `⚠️ Alert: ${payload.data.workflowName} - ${alertReason}`
    : `${payload.data.status === 'success' ? '✅' : '❌'} Workflow ${payload.data.workflowName}: ${payload.data.status}`

  const slackPayload = {
    channel: slackConfig.channelId,
    attachments: [{ color: statusColor, blocks }],
    text: fallbackText,
  }

  try {
    const response = await fetch('https://slack.com/api/chat.postMessage', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${slackAccount.accessToken}`,
      },
      body: JSON.stringify(slackPayload),
    })

    const result = await response.json()

    return { success: result.ok, error: result.ok ? undefined : result.error }
  } catch (error: unknown) {
    const err = error as Error
    return { success: false, error: err.message }
  }
}

async function updateDeliveryStatus(
  deliveryId: string,
  status: 'success' | 'failed' | 'pending',
  error?: string,
  responseStatus?: number,
  nextAttemptAt?: Date
) {
  await db
    .update(workspaceNotificationDelivery)
    .set({
      status,
      errorMessage: error || null,
      responseStatus: responseStatus || null,
      nextAttemptAt: nextAttemptAt || null,
      updatedAt: new Date(),
    })
    .where(eq(workspaceNotificationDelivery.id, deliveryId))
}

export interface NotificationDeliveryParams {
  deliveryId: string
  subscriptionId: string
  notificationType: 'webhook' | 'email' | 'slack'
  log: WorkflowExecutionLog
  alertConfig?: AlertConfig
}

export async function executeNotificationDelivery(params: NotificationDeliveryParams) {
  const { deliveryId, subscriptionId, notificationType, log, alertConfig } = params

  try {
    const [subscription] = await db
      .select()
      .from(workspaceNotificationSubscription)
      .where(eq(workspaceNotificationSubscription.id, subscriptionId))
      .limit(1)

    if (!subscription || !subscription.active) {
      logger.warn(`Subscription ${subscriptionId} not found or inactive`)
      await updateDeliveryStatus(deliveryId, 'failed', 'Subscription not found or inactive')
      return
    }

    const claimed = await db
      .update(workspaceNotificationDelivery)
      .set({
        status: 'in_progress',
        attempts: sql`${workspaceNotificationDelivery.attempts} + 1`,
        lastAttemptAt: new Date(),
        updatedAt: new Date(),
      })
      .where(
        and(
          eq(workspaceNotificationDelivery.id, deliveryId),
          eq(workspaceNotificationDelivery.status, 'pending'),
          or(
            isNull(workspaceNotificationDelivery.nextAttemptAt),
            lte(workspaceNotificationDelivery.nextAttemptAt, new Date())
          )
        )
      )
      .returning({ attempts: workspaceNotificationDelivery.attempts })

    if (claimed.length === 0) {
      logger.info(`Delivery ${deliveryId} not claimable`)
      return
    }

    const attempts = claimed[0].attempts
    const payload = await buildPayload(log, subscription)

    let result: { success: boolean; status?: number; error?: string }

    switch (notificationType) {
      case 'webhook':
        result = await deliverWebhook(subscription, payload)
        break
      case 'email':
        result = await deliverEmail(subscription, payload, alertConfig)
        break
      case 'slack':
        result = await deliverSlack(subscription, payload, alertConfig)
        break
      default:
        result = { success: false, error: 'Unknown notification type' }
    }

    if (result.success) {
      await updateDeliveryStatus(deliveryId, 'success', undefined, result.status)
      logger.info(`${notificationType} notification delivered successfully`, { deliveryId })
    } else {
      if (attempts < MAX_ATTEMPTS) {
        const retryDelay = getRetryDelayWithJitter(
          RETRY_DELAYS[attempts - 1] || RETRY_DELAYS[RETRY_DELAYS.length - 1]
        )
        const nextAttemptAt = new Date(Date.now() + retryDelay)

        await updateDeliveryStatus(
          deliveryId,
          'pending',
          result.error,
          result.status,
          nextAttemptAt
        )

        logger.info(
          `${notificationType} notification failed, scheduled retry ${attempts}/${MAX_ATTEMPTS}`,
          {
            deliveryId,
            error: result.error,
          }
        )
      } else {
        await updateDeliveryStatus(deliveryId, 'failed', result.error, result.status)
        logger.error(`${notificationType} notification failed after ${MAX_ATTEMPTS} attempts`, {
          deliveryId,
          error: result.error,
        })
      }
    }
  } catch (error) {
    logger.error('Notification delivery failed', { deliveryId, error })
    await updateDeliveryStatus(deliveryId, 'failed', 'Internal error')
  }
}

export const workspaceNotificationDeliveryTask = task({
  id: 'workspace-notification-delivery',
  retry: { maxAttempts: 1 },
  run: async (params: NotificationDeliveryParams) => executeNotificationDelivery(params),
})
